package com.ntd.study.base.jvm;

import cn.hutool.core.lang.Console;
import ntd.util.UtilProxy;

import java.util.ArrayList;

/**
 * JVM 相关内容
 * <p>
 * 1.java 内存模型 - 堆、方法区、本地内存
 * 堆：永久区、DirectBuffer 引用直接内存 - OutOfMemoryError 内存溢出（堆内存溢出）
 * 对存储的是示例对象，反射，回收算法，收集器
 * |-- 所有的实例化对象都在这里进行存储
 * |-- 堆内存对象堆所有线程可见
 * |-- 堆内存始终只有一个
 * |-- 包含了常量池（jdk 1.8）- 存储字符串常量池、静态变量
 * 方法区（永久区）：用于存储被虚拟机加载的类信息，常量，静态变量等
 * jdk 1.8 删除了方法区，改为了元空间 - 存放元数据（放在了本地内存里面）
 * |-- 可以使用MaxMetaspaceSize 对元数据区大小进行调整
 * 运行时常量池是方法区的一部分，用于存放编译期生成的各种字面变量和符号引用（jdk 1.6）（jdk 1.7 放到了堆里面了）
 * <p>
 * 方法区：java栈、本地方法栈区、程序计数器 - StackOutflowError 栈溢出（可能是栈帧数量超出或者单个栈帧内存超出）
 * 每个线程运行时是独立的，有自己栈，自己的程序计数器
 * <p>
 * 程序计数器：较小的内存空间，当前线程执行的字节码的行号指示器，各线程之间独立存储，互不影响。记录当前线程的运行状态。运行到哪个步骤。
 * java栈：线程私有的存储空间，每个方法在执行的同时会创建一个栈帧用于存储局部变量表，操作数栈，动态链接，方法出口信息等。方法的执行对应栈帧在虚拟机栈中入栈和出栈的过程，栈里面存放着各种基本数据类型和对象的引用
 * |-- 栈帧的方式存储方法调用的过程，存储基本类型的数值和引用类型的引用信息，变量出了作用域就会自动释放。
 * |-- 栈内存归属于单个线程，每个线程都会有一个栈内存
 * |-- 栈的内存远远小于堆的内存（默认值 1M），栈的深度是有限制的
 * |-- -Xss设置栈内存的大小，-Xms设置堆开始时的 大小，-Xmx设置堆内存的最大值
 * |-- 一个线程对应一个栈，所有会存在多个栈
 * 本地方法栈：保存native方法的信息，调用native方法，JVM不会在创建栈帧，JVM知识简单的动态链接并直接调用native方法。
 * <p>
 * 本地内存：指的是操作系统的实际内存，JVM的所有东西实际上都是在本地内存上运行出来的
 * NIO：这块区域会被频繁使用，directByteBuffer可以直接引用并操作本地内存
 * 这块内存可以通过MaxDirectMemorySize 来设置（默认与堆内存最大值一样），所以也会出现OOM异常
 * <p>
 * 备注：
 * 1.对永久代的调优是很困难的 - 将元空间和堆的垃圾回收进行了隔离，避免永久代引发的Full GC 和 OOM 等问题。
 * <p>
 * <p>
 * JVM 参数
 * -Xmx 堆最大内存大小配置
 * -Xms 堆最小内存大小配置
 * -XX:MaxPermSize 永久区最大内存配置 - jdk <= 1.7
 * -XX:PermSize 永久区初始内存配置 - jdk <= 1.7
 * -XX:MetaspaceSize 元空间内存大小配置 - jdk 1.8
 * -XX:MaxMetaspaceSize 元空间内存最大值配置 - jdk 1.8
 * -XX:MaxNewSize 新生代最大内存大小
 * -XX:NewSize 新生代初始内存大小
 * -Xss | -XX:ThreadStackSize 默认大小 1024kb 方法区单线程内存大小配置 - 注：没办法配置整个方法区大小
 * -XX:MaxDirectMemorySize 默认和堆内存最大值一样，直接内容容量大小配置
 * <p>
 * -XX:+HeapDumpOnOutOfMemoryError 可以让java虚拟机在出现内存溢出时产生的堆内存快照 - 快照信息能够看到当前堆里面的实例分布状况。
 * <p>
 * <p>
 * 异常：
 * OutOfMemeoryError：当堆、栈（多线程情况下）、方法去、元数据区、直接内存中数据达到最大容量时产生。
 * StackOverFlowError：线程栈的深度大于虚拟机锁允许的最大深度，其本质还是数据达到最大容量。
 * OOM：工程中类太多，而方法去或者元数据区太小（class信息，常量池，方法，静态变量），启动的时候，可能抛出OOM异常
 * 本机内存溢出：一般是和程序中的NIO有关系。
 */
public class DemoJVM {

    public static void main(String[] args) {
        UtilProxy.getProxyInstance(new ProxyClass()).test();
//        demoClassMore();
    }

    static int i = 0;

    /**
     * 测试堆空间不足异常
     * java.lang.OutOfMemoryError: Java heap space
     */
    private static void demoOutOfMemeory() {
        final ArrayList<byte[]> list = new ArrayList<>();
        while (true) {
            try {
                i++;
                list.add(new byte[1024 * 1024]);
            } catch (Throwable e) {
                Console.log(i);
                e.printStackTrace();
                return;
            }
        }
    }

    /**
     * 测试栈深度超出异常
     * java.lang.StackOverflowError
     * 一般是函数调用层级过多，比如死递归、死循环
     */
    private static void demoStackOverFlowError() {
        i++;
        try {
            demoStackOverFlowError();
        } catch (Throwable e) {
            Console.log(i);
            e.printStackTrace();
        }
    }

    /**
     * 测试栈内存溢出
     * 出现之后解决方案：
     * 1.增加物理内存
     * 2.减少堆内存的，给方法区留更多的内存
     * 3.配置每个线程的方法区内存更小
     * todo 等待完善测试
     * java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached
     */
    private static void demoStackOut() {
        while (true) {
            new Thread(() -> {
                try {
                    Thread.sleep(1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    /**
     * 测试类过多（动态代理方式） 元数据或者方法区 溢出
     */
    private static void demoClassMore() {
        while (true) {
            UtilProxy.getProxyInstance(new ProxyClass());
        }
    }

    static interface ProxyInterface { // 动态代理接口类
        void test();
    }

    static class ProxyClass implements ProxyInterface { // 动态代理实现类
        @Override
        public void test() {
            Console.log("动态代理测试");
        }
    }
}
